package com.gitee.hermer.engine.jars.starter.web.api;


import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.handler.EngineMappingRegistrationCache;

import com.gitee.hermer.boot.jee.commons.collection.CollectionUtils;
import com.gitee.hermer.boot.jee.commons.dict.BootProperties;
import com.gitee.hermer.boot.jee.commons.exception.PaiUException;
import com.gitee.hermer.boot.jee.commons.utils.DateUtils;
import com.gitee.hermer.boot.jee.commons.verify.Assert;
import com.gitee.hermer.engine.jars.app.sdk.EngineAppClassLoader;
import com.gitee.hermer.engine.jars.core.context.IEngineAppContext;
import com.gitee.hermer.engine.jars.core.context.domain.EngineAppInfoContext;
import com.gitee.hermer.engine.jars.core.context.envs.IEngineAppContextEnvironment;
import com.gitee.hermer.engine.jars.core.context.impl.EngineAppContextImpl.EngineAppLoaderState;
import com.gitee.hermer.engine.jars.core.domain.BaseInfo;
import com.gitee.hermer.engine.jars.core.domain.EngineConstant;
import com.gitee.hermer.engine.jars.core.loader.EngineAppBeanScanner;
import com.gitee.hermer.engine.jars.core.loader.event.EngineAppDestroyEvent;
import com.gitee.hermer.engine.jars.core.loader.event.EngineAppInitializationEvent;
import com.gitee.hermer.engine.jars.starter.web.base.BaseControllerHandler;

@RestController
@RequestMapping("api/execute-manager")
public class EngineApiManagerController extends BaseControllerHandler implements EnvironmentAware,ApplicationContextAware{

	private Environment environment;

	@Autowired
	private IEngineAppContext appContext;

	private ApplicationContext applicationContext;
	
	@Autowired
	private IEngineAppContextEnvironment appContextEnvironment;




	@RequestMapping("jobs")
	public BaseInfo<Map<String, Object>> jobs(){
		List<Map<String, Object>> list = CollectionUtils.newArrayList();
		List<EngineAppInfoContext> contexts =  appContext.getEnableAppModelContextList();
		for (EngineAppInfoContext appContext : contexts) {
			Map<String, Object> context = CollectionUtils.newHashMap();
			context.put(EngineConstant.BEAN_FIELD_NAME_CACKE_KEY, appContext.getCacheKey());
			context.put(EngineConstant.BEAN_FIELD_NAME_JOBSBEAN, appContext.getJobContext().getEngineBeans());
			list.add(context);
		}
		return new BaseInfo<Map<String,Object>>("", list);
	}


	@RequestMapping("appThreads")
	public BaseInfo<Map<String,String>> appThreads(){
		Set<Thread> threads = Thread.getAllStackTraces().keySet();
		Map<String,String> res = CollectionUtils.newHashMap();
		if(threads != null && (!threads.isEmpty())){
			for (Thread thread : threads) {
				if(thread.getContextClassLoader() instanceof EngineAppClassLoader){
					res.put(thread.getName(), thread.getContextClassLoader().toString());
				}
			}
		}
		return new BaseInfo<Map<String,String>>("", res);
	}

	@RequestMapping("threads")
	public BaseInfo<Map<String,String>> threads(){
		Set<Thread> threads = Thread.getAllStackTraces().keySet();
		Map<String,String> res = CollectionUtils.newHashMap();
		if(threads != null && (!threads.isEmpty())){
			for (Thread thread : threads) {
				if(thread.getContextClassLoader()!=null){
					res.put(thread.getName(), thread.getContextClassLoader().toString());
				}
			}
		}
		return new BaseInfo<Map<String,String>>("", res);
	}


	@RequestMapping("envs")
	public BaseInfo envs(){
		Map<String, Object> envs = CollectionUtils.newHashMap();
		envs.put(EngineConstant.BEAN_FIELD_NAME_APP_CONTEXTS, appContext.getEnableAppModelContextList());
		envs.put(EngineConstant.BEAN_FIELD_NAME_REQUEST_MAPPING, EngineMappingRegistrationCache.getInstance().getUrlLookup().keySet());
		envs.put(EngineConstant.BEAN_FIELD_NAME_ENGINE_ENVS, appContextEnvironment.getEngineAppContextEnvironment());
		envs.putAll(System.getenv());
		return new BaseInfo<Map>("",envs);
	}

	@RequestMapping("destroy")
	public BaseInfo destroy(@RequestParam String cacheKey) throws Exception{
		Assert.hasText(cacheKey);
		return new BaseInfo("", destroyAppContext(cacheKey));

	}


	@RequestMapping("start")
	public BaseInfo<EngineAppInitializationEvent> start(@RequestParam String fileName) throws InterruptedException, IOException{
		return new BaseInfo<EngineAppInitializationEvent>("", publishEngineAppInitializationEvent(fileName));
	}


	@RequestMapping("restart")
	public BaseInfo<String> restart(@RequestParam String cacheKey) throws PaiUException, InterruptedException, IOException{
		EngineAppInfoContext context = appContext.getEnableAppModelContext(cacheKey);
		Assert.isTrue(destroyAppContext(cacheKey));
		startAppContext(context.getJarFileName());
		return new BaseInfo<String>("", context.getCacheKey());
	}
	
	final EngineAppInitializationEvent publishEngineAppInitializationEvent(String fileName) throws IOException{
		Assert.hasText(fileName);
		EngineAppInitializationEvent event = EngineAppBeanScanner.createEngineAppInitializationEvent(EngineAppBeanScanner.getMonitorWorkJarFiles(fileName));
		applicationContext.publishEvent(event);
		return event;
	}

	final EngineAppInfoContext startAppContext(String fileName) throws InterruptedException, IOException{
		EngineAppInitializationEvent event = publishEngineAppInitializationEvent(fileName);
		int i= 0;
		while (appContext.getCurrentLoaderState(event.getCacheKey()) != EngineAppLoaderState.RUN_ING && i < 8) {
			i += 1;
			Thread.currentThread().sleep(500);
		}
		return appContext.getEnableAppModelContext(event.getCacheKey());
	}



	@RequestMapping("list")
	public BaseInfo<EngineAppInfoContext> list(){
		try{
			List<EngineAppInfoContext> appInfoContexts = CollectionUtils.newArrayList(appContext.getEnableAppModelContextList());
			File[] jars = EngineAppBeanScanner.getMonitorWorkJarFiles();
			for (File file : jars) {
				try{
					EngineAppInitializationEvent event = EngineAppBeanScanner.createEngineAppInitializationEvent(file);
					if(!appInfoContexts.contains(new EngineAppInfoContext(event.getCacheKey()))){
						appInfoContexts.add(new EngineAppInfoContext(event.getCacheKey(), file.getName(),
								event.getFileMd5(), DateUtils.getDateTimeFormat(new Date(file.lastModified()))));
					}
				}catch (Exception e) {
					error(e.getMessage(),e);
				}
			}
			return new BaseInfo<EngineAppInfoContext>("", appInfoContexts);
		}catch (PaiUException e) {
			return new BaseInfo<EngineAppInfoContext>("", e);
		}
		catch (Exception e) {
			return new BaseInfo<EngineAppInfoContext>("", false);
		}
	}


	final boolean destroyAppContext(String cacheKey) throws InterruptedException{
		Assert.hasText(cacheKey);
		EngineAppInfoContext context = appContext.getEnableAppModelContext(cacheKey);
		Assert.notNull(context);

		EngineAppDestroyEvent event = new EngineAppDestroyEvent(cacheKey,(EngineAppClassLoader)context.getClassLoader());
		applicationContext.publishEvent(event);
		int i= 0;
		while (appContext.getCurrentLoaderState(event.getCacheKey()) != null && i < 8) {
			i += 1;
			Thread.currentThread().sleep(500);
		}
		return appContext.getCurrentLoaderState(event.getCacheKey()) == null;
	}


	@Override
	public void setEnvironment(Environment environment) {
		this.environment = environment;
	}


	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}




}
